Learn in 10 minutes

Learn in 10 minutes

Aprende Go en 10 minutos

Go (también conocido como Golang) es un lenguaje de programación compilado y tipado estáticamente diseñado en Google. Es conocido por su simplicidad, eficiencia y excelente soporte para concurrencia. Este tutorial te ayudará a aprender programación en Go rápidamente.

1. Escribiendo tu primer programa en Go

Comencemos con un programa simple. Crea un archivo llamado hello.go e ingresa el siguiente código:

package main

import "fmt"

func main() {
    fmt.Println("¡Hola, Mundo!")
}

Guarda el archivo y ejecuta el siguiente comando en la terminal:

go run hello.go

La salida será:

¡Hola, Mundo!

Este programa simple demuestra la estructura básica de Go:

  • package main declara el nombre del paquete
  • import "fmt" importa el paquete de formato para operaciones de E/S
  • func main() es el punto de entrada del programa
  • fmt.Println() imprime texto en la consola

2. Sintaxis básica

Go tiene una sintaxis limpia y simple. A diferencia de Python, Go usa llaves {} para definir bloques de código y requiere punto y coma al final de las declaraciones (aunque generalmente se insertan automáticamente).

// Este es un comentario de una línea
fmt.Println("¡Hola, Mundo!")

/*
Este es un comentario de múltiples líneas
que abarca varias líneas
*/

Reglas básicas de sintaxis en Go:

  • Bloques de código: Definidos por llaves {}
  • Comentarios: Los comentarios de una línea comienzan con //, los de múltiples líneas con /* */
  • Declaraciones: Terminan con punto y coma (insertado automáticamente)
  • Declaración de paquete: Cada archivo comienza con una declaración de paquete

3. Variables y tipos de datos

Go es tipado estáticamente, lo que significa que debes declarar los tipos de variables. Sin embargo, Go admite inferencia de tipos con el operador :=.

Métodos de declaración de variables:

// Declaración de tipo explícita
var nombre string = "Juan"
var edad int = 25

// Inferencia de tipos
nombre := "Juan"
edad := 25

// Declaración múltiple de variables
var x, y int = 10, 20
x, y := 10, 20

Tipos de datos básicos principales de Go:

  • Tipos enteros: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
  • Tipos flotantes: float32, float64
  • Cadena: string
  • Booleano: bool
  • Tipos complejos: complex64, complex128

3.1 Tipos numéricos

Go proporciona varios tipos numéricos para diferentes casos de uso:

// Tipos enteros
var edad int = 25
var numeroPequeño int8 = 127
var numeroGrande int64 = 9223372036854775807

// Tipos flotantes
var temperatura float32 = 36.5
var pi float64 = 3.14159265359

// Números complejos
var numeroComplejo complex64 = 3 + 4i

3.2 Tipo cadena

Las cadenas en Go son secuencias de bytes y son inmutables:

// Declaración de cadena
var saludo string = "¡Hola, Go!"
nombre := "Alicia"

// Operaciones con cadenas
fmt.Println(len(saludo))        // Longitud de la cadena
fmt.Println(saludo[0])          // Acceder al primer carácter (byte)
fmt.Println(saludo[0:5])        // Segmentación de cadena
fmt.Println(strings.ToUpper(nombre)) // Convertir a mayúsculas

3.3 Tipo booleano

El tipo booleano tiene dos valores: true y false:

var estaActivo bool = true
var estaCompletado bool = false

// Operaciones booleanas
resultado1 := true && false  // false
resultado2 := true || false  // true
resultado3 := !true          // false

4. Constantes

Las constantes se declaran usando la palabra clave const y no se pueden cambiar:

const Pi = 3.14159
const MaxUsuarios = 1000

// Múltiples constantes
const (
    EstadoOK = 200
    EstadoNoEncontrado = 404
    EstadoError = 500
)

// Constantes tipadas
const Version string = "1.0.0"

5. Estructuras de datos

Go proporciona varias estructuras de datos integradas para almacenar y manipular datos.

5.1 Arreglos

Los arreglos son secuencias de tamaño fijo de elementos del mismo tipo:

// Declaración de arreglo
var numeros [5]int = [5]int{1, 2, 3, 4, 5}
nombres := [3]string{"Alicia", "Bob", "Carlos"}

// Accediendo a elementos
fmt.Println(numeros[0])  // 1
numeros[0] = 10         // Modificar elemento

// Longitud del arreglo
fmt.Println(len(numeros)) // 5

5.2 Slices

Los slices son arreglos dinámicos que pueden crecer y reducirse:

// Declaración de slice
numeros := []int{1, 2, 3, 4, 5}
var sliceVacio []int

// Creando slices desde arreglos
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // [2, 3, 4]

// Operaciones con slices
numeros = append(numeros, 6)      // Agregar elemento
numeros = append(numeros, 7, 8, 9) // Agregar múltiples elementos

// Capacidad y longitud del slice
fmt.Println(len(numeros)) // Longitud: 9
fmt.Println(cap(numeros)) // Capacidad: 10 (puede variar)

5.3 Mapas

Los mapas son colecciones desordenadas de pares clave-valor:

// Declaración de mapa
estudiante := map[string]interface{}{
    "nombre": "Juan",
    "edad":  20,
    "carrera": "Ciencias de la Computación",
}

// Declaración alternativa
var puntajes map[string]int = make(map[string]int)
puntajes["matemáticas"] = 95
puntajes["ciencias"] = 88

// Accediendo y modificando
fmt.Println(estudiante["nombre"])
estudiante["edad"] = 21
estudiante["promedio"] = 3.8

// Acceso seguro
if telefono, existe := estudiante["teléfono"]; existe {
    fmt.Println(telefono)
} else {
    fmt.Println("Teléfono no proporcionado")
}

// Iterando sobre el mapa
for clave, valor := range estudiante {
    fmt.Printf("%s: %v\n", clave, valor)
}

5.4 Structs

Los structs son colecciones de campos que pueden tener diferentes tipos:

// Definición de struct
type Persona struct {
    Nombre string
    Edad  int
    Ciudad string
}

// Creando instancias de struct
persona1 := Persona{"Alicia", 25, "Nueva York"}
persona2 := Persona{
    Nombre: "Bob",
    Edad:  30,
    Ciudad: "Londres",
}

// Accediendo a campos
fmt.Println(persona1.Nombre)
persona1.Edad = 26

6. Operaciones y operadores

Go proporciona un conjunto completo de operadores para varios cálculos y comparaciones.

  • Operadores aritméticos: +, -, *, /, % (módulo)
  • Operadores de comparación: ==, !=, >, <, >=, <=
  • Operadores lógicos: &&, ||, !
  • Operadores bit a bit: &, |, ^, <<, >>
  • Operadores de asignación: =, +=, -=, *=, /=

6.1 Operadores aritméticos

a, b := 10, 3

fmt.Printf("Suma: %d\n", a + b)      // 13
fmt.Printf("Resta: %d\n", a - b)   // 7
fmt.Printf("Multiplicación: %d\n", a * b)  // 30
fmt.Printf("División: %d\n", a / b)      // 3
fmt.Printf("Módulo: %d\n", a % b)      // 1

6.2 Operadores de comparación

x, y := 5, 10

fmt.Printf("Igual: %t\n", x == y)     // false
fmt.Printf("No igual: %t\n", x != y) // true
fmt.Printf("Mayor que: %t\n", x > y)  // false
fmt.Printf("Menor que: %t\n", x < y)  // true

6.3 Operadores lógicos

a, b := true, false

fmt.Printf("Operación AND: %t\n", a && b)  // false
fmt.Printf("Operación OR: %t\n", a || b)    // true
fmt.Printf("Operación NOT: %t\n", !a)    // false

7. Control de flujo

Go proporciona varias declaraciones de control de flujo para gestionar la ejecución del programa.

7.1 Declaraciones if

edad := 20
if edad >= 18 {
    fmt.Println("Adulto")
} else if edad >= 13 {
    fmt.Println("Adolescente")
} else {
    fmt.Println("Niño")
}

// if con declaración corta
if puntaje := 85; puntaje >= 90 {
    fmt.Println("Calificación: A")
} else if puntaje >= 80 {
    fmt.Println("Calificación: B")
} else {
    fmt.Println("Calificación: C")
}

7.2 Bucles for

Go tiene solo una construcción de bucle: for

// Bucle for básico
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// Bucle estilo while
contador := 0
for contador < 5 {
    fmt.Println(contador)
    contador++
}

// Bucle infinito
for {
    fmt.Println("Esto se ejecutará para siempre")
    break // Usar break para salir
}

// Bucle range (para slices, arreglos, mapas)
frutas := []string{"manzana", "banana", "cereza"}
for indice, fruta := range frutas {
    fmt.Printf("%d: %s\n", indice, fruta)
}

7.3 Declaraciones switch

dia := "Lunes"
switch dia {
case "Lunes":
    fmt.Println("Inicio de la semana")
case "Viernes":
    fmt.Println("El fin de semana está cerca")
case "Sábado", "Domingo":
    fmt.Println("¡Fin de semana!")
default:
    fmt.Println("Día regular")
}

// Switch sin expresión
puntaje := 85
switch {
case puntaje >= 90:
    fmt.Println("Calificación: A")
case puntaje >= 80:
    fmt.Println("Calificación: B")
case puntaje >= 70:
    fmt.Println("Calificación: C")
default:
    fmt.Println("Calificación: F")
}

8. Funciones

Las funciones en Go son ciudadanos de primera clase y admiten múltiples valores de retorno.

8.1 Funciones básicas

func saludar(nombre string) string {
    return "¡Hola, " + nombre + "!"
}

// Llamando la función
mensaje := saludar("Juan")
fmt.Println(mensaje)

8.2 Múltiples valores de retorno

func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("no se puede dividir por cero")
    }
    return a / b, nil
}

// Usando múltiples valores de retorno
resultado, err := dividir(10, 2)
if err != nil {
    fmt.Println("Error:", err)
} else {
    fmt.Println("Resultado:", resultado)
}

8.3 Valores de retorno nombrados

func calcularRectangulo(ancho, altura float64) (area float64, perimetro float64) {
    area = ancho * altura
    perimetro = 2 * (ancho + altura)
    return // retorno desnudo
}

area, perimetro := calcularRectangulo(5, 3)
fmt.Printf("Área: %.2f, Perímetro: %.2f\n", area, perimetro)

8.4 Funciones variádicas

func sumar(numeros ...int) int {
    total := 0
    for _, num := range numeros {
        total += num
    }
    return total
}

fmt.Println(sumar(1, 2, 3, 4))  // 10
fmt.Println(sumar(5, 10, 15))   // 30

9. Punteros

Go tiene punteros pero con una sintaxis más simple que C/C++:

func modificarValor(x *int) {
    *x = *x * 2
}

func main() {
    valor := 10
    fmt.Println("Antes:", valor) // 10

    modificarValor(&valor)
    fmt.Println("Después:", valor)  // 20
}

10. Métodos

Los métodos son funciones con un argumento receptor:

type Rectangulo struct {
    Ancho  float64
    Altura float64
}

// Método con receptor de valor
func (r Rectangulo) Area() float64 {
    return r.Ancho * r.Altura
}

// Método con receptor de puntero
func (r *Rectangulo) Escalar(factor float64) {
    r.Ancho *= factor
    r.Altura *= factor
}

rect := Rectangulo{Ancho: 5, Altura: 3}
fmt.Println("Área:", rect.Area()) // 15

rect.Escalar(2)
fmt.Println("Área escalada:", rect.Area()) // 60

11. Interfaces

Las interfaces definen firmas de métodos que los tipos pueden implementar:

type Figura interface {
    Area() float64
    Perimetro() float64
}

type Circulo struct {
    Radio float64
}

func (c Circulo) Area() float64 {
    return 3.14159 * c.Radio * c.Radio
}

func (c Circulo) Perimetro() float64 {
    return 2 * 3.14159 * c.Radio
}

func imprimirInfoFigura(f Figura) {
    fmt.Printf("Área: %.2f, Perímetro: %.2f\n", f.Area(), f.Perimetro())
}

circulo := Circulo{Radio: 5}
imprimirInfoFigura(circulo)

12. Manejo de errores

Go usa manejo explícito de errores en lugar de excepciones:

func leerArchivo(nombreArchivo string) (string, error) {
    datos, err := os.ReadFile(nombreArchivo)
    if err != nil {
        return "", fmt.Errorf("falló al leer el archivo %s: %w", nombreArchivo, err)
    }
    return string(datos), nil
}

contenido, err := leerArchivo("ejemplo.txt")
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Contenido:", contenido)

13. Concurrencia con Goroutines

Las goroutines son hilos ligeros administrados por el runtime de Go:

func trabajador(id int) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Trabajador %d: %d\n", id, i)
        time.Sleep(time.Millisecond * 100)
    }
}

func main() {
    // Iniciar múltiples goroutines
    for i := 1; i <= 3; i++ {
        go trabajador(i)
    }

    // Esperar a que las goroutines completen
    time.Sleep(time.Second)
    fmt.Println("Todos los trabajadores completaron")
}

14. Canales

Los canales se utilizan para la comunicación entre goroutines:

func productor(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i // Enviar valor al canal
        time.Sleep(time.Millisecond * 100)
    }
    close(ch) // Cerrar canal cuando termine
}

func consumidor(ch <-chan int) {
    for valor := range ch {
        fmt.Println("Recibido:", valor)
    }
}

func main() {
    ch := make(chan int, 3) // Canal con buffer

    go productor(ch)
    consumidor(ch)

    fmt.Println("Comunicación por canal completada")
}

15. Operaciones con archivos

Go proporciona métodos simples para leer y escribir archivos:

// Leyendo archivos
datos, err := os.ReadFile("ejemplo.txt")
if err != nil {
    fmt.Println("Error leyendo archivo:", err)
    return
}
fmt.Println("Contenido del archivo:", string(datos))

// Escribiendo archivos
contenido := "¡Hola, Go!\n"
err = os.WriteFile("salida.txt", []byte(contenido), 0644)
if err != nil {
    fmt.Println("Error escribiendo archivo:", err)
    return
}
fmt.Println("Archivo escrito exitosamente")

16. Paquetes y módulos

Los módulos de Go gestionan dependencias y versiones de paquetes:

// Importando paquetes de la biblioteca estándar
import (
    "fmt"
    "math"
    "strings"
)

func main() {
    fmt.Println(math.Sqrt(16))        // 4
    fmt.Println(strings.ToUpper("go")) // GO
}

Para crear tu propio paquete, crea un directorio con el nombre de tu paquete y exporta funciones capitalizando sus nombres.

17. Pruebas

Go tiene soporte integrado para pruebas:

// En el archivo math_test.go
package main

import "testing"

func TestSumar(t *testing.T) {
    resultado := sumar(2, 3)
    esperado := 5
    if resultado != esperado {
        t.Errorf("sumar(2, 3) = %d; se esperaba %d", resultado, esperado)
    }
}

func sumar(a, b int) int {
    return a + b
}

Ejecuta las pruebas con: go test

18. Mejores prácticas

  • Usa gofmt para formatear tu código
  • Sigue las convenciones de nomenclatura de Go (camelCase para variables, PascalCase para exportaciones)
  • Maneja errores explícitamente
  • Usa interfaces para abstracción
  • Prefiere composición sobre herencia
  • Escribe pruebas completas
  • Usa la biblioteca estándar siempre que sea posible

Este tutorial cubre las características esenciales de la programación en Go. Con práctica, podrás construir aplicaciones eficientes y concurrentes usando las poderosas características de Go.